/*
 * @(#)EnglishDocumentIndexFactory.java, 2024/2/18 17:38
 *
 * Copyright (c) 2018-2024, 四川蜀天梦图数据科技有限公司.
 * All rights reserved.
 */
package controllers;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.search.similarities.BM25Similarity;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.NumericUtils;

import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * EnglishDocumentIndexFactory
 *
 * @author fantazy
 * @version 1.0, 2024年02月18日
 */
public class EnglishDocumentIndexFactory
{
    Directory directory;
    Analyzer analyzer;
    static String INDEX_HOME = "f:/lucene/index";
    IndexWriter iw;
    SearcherManager sm;
    QueryTimeListener queryTimeListener;
    SearchContext sc;

    public EnglishDocumentIndexFactory() throws IOException
    {
        directory = FSDirectory.open(Paths.get(INDEX_HOME));
        analyzer = new StandardAnalyzer();
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        config.setSimilarity(new BM25Similarity());
        config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
        iw = new IndexWriter(directory, config);
        sm = new SearcherManager(iw, true, null);
        queryTimeListener = new QueryTimeListener();
        sc = new SearchContext(queryTimeListener);
    }

    public void writeIntoLucene(String title, String content) throws IOException
    {
        Article doc = new Article(title, content);
        writeArticle(doc);
    }

    public Collection<Article> searchDocuments(String queryTxt) throws IOException, ParseException
    {

        IndexReader ir = DirectoryReader.open(iw, false);
        IndexSearcher is = new IndexSearcher(ir);
        QueryParser parser = new QueryParser("content", analyzer);
        Query query = parser.parse(queryTxt);
        final ScoreDoc[] scoreDocs = sc.doLuceneSearch(is, query);
        List<Article> articles = new ArrayList<>(scoreDocs.length);
        for (ScoreDoc doc : scoreDocs)
        {
            final Document record = is.doc(doc.doc);
            final int no = record.getField("no").numericValue().intValue();
            final String title = record.get("title");
            final String content = record.get("content");
            articles.add(new Article(no, title, content, doc.score));
        }
        System.out.println(queryTimeListener.getQueryTimeUsed());
        return articles;
    }

    public Article findUniqueArticleViaTitle(Integer no, String queryTxt) throws IOException, ParseException, InvalidTokenOffsetsException
    {
        BytesRefBuilder bytesBuffer = new BytesRefBuilder();
        NumericUtils.intToPrefixCoded(no, 0, bytesBuffer);
        Query query = new TermQuery(new Term("no", bytesBuffer));
        IndexReader ir = DirectoryReader.open(iw, false);
        IndexSearcher is = new IndexSearcher(ir);
        final ScoreDoc[] docs = sc.doLuceneSearch(is, query);
        if (0 == docs.length)
        {
            return null;
        }
        final Document doc = is.doc(docs[0].doc);
        String content = doc.get("content");
        //关键词搜索
        QueryParser parser = new QueryParser("content", analyzer);
        Query q = parser.parse(queryTxt);
        //高亮组件
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(q, ir, "content"));
        highlighter.setTextFragmenter(new SimpleFragmenter(content.length()));
        String bestFragment = "";
        if (content != null)
        {
            TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(content));
            bestFragment = highlighter.getBestFragment(tokenStream, content);
            System.out.println(bestFragment);
        }
        System.out.println(queryTimeListener.getQueryTimeUsed());
        return new Article(doc.getField("no").numericValue().intValue(),
                doc.get("title"),
                bestFragment,
                docs[0].score);
    }

    private void writeArticle(Article article) throws IOException
    {
        if (iw.isOpen())
        {
            Document document = new Document();
            //for sorting not stored
            document.add(new NumericDocValuesField("id", article.getNo()));
            // no equals id
            document.add(new IntField("no", article.getNo(), IntField.TYPE_STORED));
            document.add(new Field("title", article.getTitle(), TextField.TYPE_STORED));
            document.add(new Field("content", article.getContent(), TextField.TYPE_STORED));
            iw.addDocument(document);
            iw.flush();
            iw.commit();
        }
    }
}
